﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Windows.Controls;
using System.Windows;
using System.Web;
using System.Globalization;

namespace JJCraft
{
    class LogParseResult
    {
        public LogParseResult()
        {
            Combats = new List<Combat>();
        }

        public List<Combat> Combats { get; set; }
    }

    enum UnitType
    {
        None,
        Player,
        Pet
    }

    enum UnitReaction
    {
        Neutral,
        Friendly,
        Hostile
    }

    enum UnitClass
    {
        None,
        Warrior,
        Hunter,
        Shaman,
        Paladin,
        Warlock,
        Priest,
        Rogue,
        Mage,
        Druid,
        DeathKnight
    }

    class Unit
    {
        private Dictionary<UnitReaction, int> ReactionProofs;
        private Dictionary<UnitType, int> TypeProofs;
        private Dictionary<UnitClass, int> ClassProofs;

        public UnitReaction Reaction;
        public UnitType Type;
        public UnitClass Class;

        public long GUID;
        public string Name;
        public bool IsWellFormed;
        public bool IsActive;

        public List<LogLine> DamageIn;
        public List<LogLine> HealIn;
        public List<LogLine> ActionIn;
        public List<LogLine> DamageOut;
        public List<LogLine> HealOut;
        public List<LogLine> ActionOut;

        public DateTime StartTime;
        public DateTime EndTime;

        public Combat Combat;

        public Dictionary<long, bool> Relationships;

        public Unit(long guid, string name, Combat combat)
        {
            Combat = combat;
            GUID = guid;
            Name = name;
            IsWellFormed = false;
            IsActive = false;

            ReactionProofs = new Dictionary<UnitReaction, int>();
            TypeProofs = new Dictionary<UnitType, int>();
            ClassProofs = new Dictionary<UnitClass, int>();

            Relationships = new Dictionary<long, bool>();

            DamageIn = new List<LogLine>();
            HealIn = new List<LogLine>();
            ActionIn = new List<LogLine>();
            DamageOut = new List<LogLine>();
            HealOut = new List<LogLine>();
            ActionOut = new List<LogLine>();
        }

        public void ProveClass(UnitClass unitClass)
        {
            if (!ClassProofs.ContainsKey(unitClass))
            {
                ClassProofs.Add(unitClass, 0);
            }

            ClassProofs[unitClass]++;
        }

        public void ProveReaction(UnitReaction reaction)
        {
            if (!ReactionProofs.ContainsKey(reaction))
            {
                ReactionProofs.Add(reaction, 0);
            }

            ReactionProofs[reaction]++;
        }

        public void ProveType(UnitType type)
        {
            if (!TypeProofs.ContainsKey(type))
            {
                TypeProofs.Add(type, 0);
            }

            TypeProofs[type]++;
        }

        public void ProveRelationship(long relGuid)
        {
            Relationships[relGuid] = true;
        }

        public void EndActivity()
        {
            if (DamageIn.Count + DamageOut.Count + HealIn.Count + HealOut.Count + ActionIn.Count + ActionOut.Count > 6 && (EndTime - StartTime).TotalSeconds > 2)
            {
                IsActive = true;
            }
        }

        public void End()
        {
            if (TypeProofs.Count > 0)
            {
                Type = TypeProofs.OrderByDescending(pair => pair.Value).ElementAt(0).Key;
            }
            else
            {
                Type = UnitType.None;
            }

            if (ReactionProofs.Count > 0)
            {
                Reaction = ReactionProofs.OrderByDescending(pair => pair.Value).ElementAt(0).Key;
            }
            else
            {
                Reaction = UnitReaction.Neutral;
            }

            if (ClassProofs.Count > 0)
            {
                Class = ClassProofs.OrderByDescending(pair => pair.Value).ElementAt(0).Key;
            }
            else
            {
                Class = UnitClass.None;
            }

            bool isRelatedToActiveUnit = false;
            foreach (long guid in Relationships.Keys)
            {
                if (Combat.Units[guid].IsActive)
                {
                    isRelatedToActiveUnit = true;
                }
            }

            if (Class != UnitClass.None && Type != UnitType.None && Reaction != UnitReaction.Neutral && (isRelatedToActiveUnit || IsActive))
            {
                IsWellFormed = true;
            }
        }
    }

    enum CombatResult
    {
        Win,
        Lose,
        DrawGame
    }

    class Combat
    {
        public bool IsWellFormed { get; set; }
        public DateTime StartTime { get; set; }
        public DateTime EndTime { get; set; }
        public Dictionary<long, Unit> Units { get; set; }

        public List<Unit> Friends { get; set; }
        public List<Unit> Enemies { get; set; }

        private UnitReaction LastDeathReaction;
        public CombatResult Result;

        public Combat()
        {
            IsWellFormed = false;
            StartTime = DateTime.MinValue;
            EndTime = DateTime.MaxValue;
            Units = new Dictionary<long, Unit>();
            LastDeathReaction = UnitReaction.Neutral;
        }

        public void ReadLog(LogLine logLine)
        {
            if (StartTime == DateTime.MinValue)
            {
                StartTime = logLine.Time;
            }
            EndTime = logLine.Time;

            string eventType = logLine.Parameters[0];

            long srcGUID = long.Parse(logLine.Parameters[1].Substring(2), System.Globalization.NumberStyles.HexNumber);
            string srcName = GetName(logLine.Parameters[2]);
            long srcFlag = long.Parse(logLine.Parameters[3].Substring(2), System.Globalization.NumberStyles.HexNumber);

            long destGUID = long.Parse(logLine.Parameters[4].Substring(2), System.Globalization.NumberStyles.HexNumber);
            string destName = GetName(logLine.Parameters[5]);
            long destFlag = long.Parse(logLine.Parameters[6].Substring(2), System.Globalization.NumberStyles.HexNumber);

            if (!Units.ContainsKey(srcGUID))
            {
                Units[srcGUID] = new Unit(srcGUID, srcName, this)
                {
                    StartTime = logLine.Time
                };
            }

            if (!Units.ContainsKey(destGUID))
            {
                Units[destGUID] = new Unit(destGUID, destName, this)
                {
                    StartTime = logLine.Time
                };
            }

            Units[srcGUID].EndTime = logLine.Time;
            Units[destGUID].EndTime = logLine.Time;

            Units[srcGUID].ProveType(GetUnitType(srcFlag));
            Units[destGUID].ProveType(GetUnitType(destFlag));

            Units[srcGUID].ProveReaction(GetUnitReaction(srcFlag));
            Units[destGUID].ProveReaction(GetUnitReaction(destFlag));

            if (destGUID != 0 && destName != "nil")
            {
                Units[srcGUID].ProveRelationship(destGUID);
            }

            if (srcGUID != 0 && srcName != "nil")
            {
                Units[destGUID].ProveRelationship(srcGUID);
            }

            if (eventType == "SPELL_CAST_SUCCESS")
            {
                string spellId = logLine.Parameters[7];

                if (ClassManager.Singleton.ClassProofs.ContainsKey(spellId))
                {
                    Units[srcGUID].ProveClass(ClassManager.Singleton.ClassProofs[spellId]);
                }
            }

            if (eventType == "UNIT_DIED" && GetUnitType(destFlag) == UnitType.Player && GetUnitReaction(destFlag) != UnitReaction.Neutral)
            {
                LastDeathReaction = GetUnitReaction(destFlag);
            }

            switch (eventType)
            {
                case "SWING_DAMAGE":
                case "SWING_MISSED":
                case "RANGE_DAMAGE":
                case "RANGE_MISSED":
                case "SPELL_DAMAGE":
                case "SPELL_MISSED":
                case "SPELL_PERIODIC_DAMAGE":
                case "SPELL_PERIODIC_MISSED":
                case "DAMAGE_SHIELD":
                case "DAMAGE_SHIELD_MISSED":
                case "DAMAGE_SPLIT":
                case "ENVIRONMENTAL_DAMAGE":
                case "SPELL_DRAIN":
                case "SPELL_LEECH":
                case "SPELL_PERIODIC_DRAIN":
                case "SPELL_PERIODIC_LEECH":
                    {
                        if (srcGUID != destGUID)
                        {
                            Units[srcGUID].DamageOut.Add(logLine);
                        }
                        Units[destGUID].DamageIn.Add(logLine);
                    }
                    break;
                case "SPELL_HEAL":
                case "SPELL_PERIODIC_HEAL":
                    {
                        Units[srcGUID].HealOut.Add(logLine);
                        Units[destGUID].HealIn.Add(logLine);
                    }
                    break;
                case "SPELL_AURA_APPLIED":
                case "SPELL_AURA_APPLIED_DOSE":
                case "SPELL_AURA_REFRESH":
                    {
                        Units[srcGUID].ActionOut.Add(logLine);
                        Units[destGUID].ActionIn.Add(logLine);
                    }
                    break;
                case "SPELL_AURA_REMOVED":
                case "SPELL_AURA_REMOVED_DOSE":
                    {
                        Units[destGUID].ActionIn.Add(logLine);
                    }
                    break;
                case "SPELL_INTERRUPT":
                case "SPELL_STOLEN":
                case "SPELL_DISPEL":
                case "SPELL_DISPEL_FAILED":
                case "SPELL_EXTRA_ATTACKS":
                case "SPELL_AURA_BROKEN":
                case "SPELL_AURA_BROKEN_SPELL":
                case "UNIT_DIED":
                    {
                        Units[srcGUID].ActionOut.Add(logLine);
                        Units[destGUID].ActionIn.Add(logLine);
                    }
                    break;
                case "SPELL_CAST_SUCCESS":
                    {
                    }
                    break;
                case "SPELL_PERIODIC_ENERGIZE":
                case "SPELL_ENERGIZE":
                    {
                        Units[destGUID].ActionIn.Add(logLine);
                    }
                    break;
            }
        }

        public static string GetName(string nameWithQuote)
        {
            return nameWithQuote.Trim('"');
        }

        private UnitType GetUnitType(long flag)
        {
            long masked = flag & 0x0000FC00;
            switch (masked)
            {
                case 0x00001000:
                    return UnitType.Pet;
                case 0x00000400:
                    return UnitType.Player;
                default:
                    return UnitType.None;
            }
        }

        private UnitReaction GetUnitReaction(long flag)
        {
            long masked = flag & 0x000000F0;
            switch (masked)
            {
                case 0x00000040:
                    return UnitReaction.Hostile;
                case 0x00000010:
                    return UnitReaction.Friendly;
                default:
                    return UnitReaction.Neutral;
            }
        }

        public void End()
        {
            foreach (Unit unit in Units.Values)
            {
                unit.EndActivity();
            }

            foreach (Unit unit in Units.Values)
            {
                unit.End();
            }

            Friends = new List<Unit>();
            Enemies = new List<Unit>();
            foreach (Unit unit in this.Units.Values)
            {
                if (unit.IsWellFormed && unit.Type == UnitType.Player)
                {
                    if (unit.Reaction == UnitReaction.Friendly)
                    {
                        Friends.Add(unit);
                    }
                    else if (unit.Reaction == UnitReaction.Hostile)
                    {
                        Enemies.Add(unit);
                    }
                }
            }

            if (Friends.Count > 0 && Enemies.Count > 0)
            {
                IsWellFormed = true;
            }

            if (LastDeathReaction == UnitReaction.Neutral)
            {
                Result = CombatResult.DrawGame;
            }
            else if (LastDeathReaction == UnitReaction.Friendly)
            {
                Result = CombatResult.Lose;
            }
            else if (LastDeathReaction == UnitReaction.Hostile)
            {
                Result = CombatResult.Win;
            }
        }

        public string GenerateReport()
        {
            string result = string.Empty;

            //try
            //{
                string reportTemplate = File.ReadAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ReportTemplates\\ReportTemplate.txt"));
                string playerTemplate = File.ReadAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ReportTemplates\\PlayerTemplate.txt"));
                string playerNameTemplate = File.ReadAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ReportTemplates\\PlayerNameTemplate.txt"));
                string playerReportTemplate = File.ReadAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ReportTemplates\\PlayerReportTemplate.txt"));
                string eventTemplate = File.ReadAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ReportTemplates\\EventTemplate.txt"));
                string eventItemTemplate = File.ReadAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ReportTemplates\\EventItemTemplate.txt"));

                StringBuilder reportBuilder = new StringBuilder();
                reportBuilder.Append(reportTemplate);

                reportBuilder.Replace("{%title%}", "JJCraft 战役报告");
                switch (Result)
                {
                    case CombatResult.Win:
                        reportBuilder.Replace("{%result%}", "胜");
                        break;
                    case CombatResult.Lose:
                        reportBuilder.Replace("{%result%}", "败");
                        break;
                    case CombatResult.DrawGame:
                        reportBuilder.Replace("{%result%}", "平局");
                        break;
                }

                StringBuilder friendsBuilder = new StringBuilder();
                foreach (Unit friend in Friends)
                {
                    StringBuilder friendBuilder = new StringBuilder();
                    friendBuilder.Append(playerTemplate);

                    StringBuilder friendNameBuilder = new StringBuilder();
                    friendNameBuilder.Append(playerNameTemplate);
                    friendNameBuilder.Replace("{%guid%}", friend.GUID.ToString());
                    friendNameBuilder.Replace("{%class%}", friend.Class.ToString());
                    friendNameBuilder.Replace("{%name%}", HttpUtility.HtmlEncode(friend.Name));

                    friendBuilder.Replace("{%playername%}", friendNameBuilder.ToString());

                    friendsBuilder.Append(friendBuilder.ToString());
                }

                reportBuilder.Replace("{%friends%}", friendsBuilder.ToString());

                StringBuilder enemiesBuilder = new StringBuilder();
                foreach (Unit enemy in Enemies)
                {
                    StringBuilder enemyBuilder = new StringBuilder();
                    enemyBuilder.Append(playerTemplate);

                    StringBuilder enemyNameBuilder = new StringBuilder();
                    enemyNameBuilder.Append(playerNameTemplate);
                    enemyNameBuilder.Replace("{%guid%}", enemy.GUID.ToString());
                    enemyNameBuilder.Replace("{%class%}", enemy.Class.ToString());
                    enemyNameBuilder.Replace("{%name%}", HttpUtility.HtmlEncode(enemy.Name));

                    enemyBuilder.Replace("{%playername%}", enemyNameBuilder.ToString());

                    enemiesBuilder.Append(enemyBuilder.ToString());
                }

                reportBuilder.Replace("{%enemies%}", enemiesBuilder.ToString());

                StringBuilder playerReportsBuilder = new StringBuilder();
                foreach (Unit unit in Units.Values)
                {
                    if (unit.IsWellFormed && unit.Type == UnitType.Player && unit.Class != UnitClass.None)
                    {
                        StringBuilder playerReportBuilder = new StringBuilder();
                        playerReportBuilder.Append(playerReportTemplate);

                        playerReportBuilder.Replace("{%guid%}", unit.GUID.ToString());
                        playerReportBuilder.Replace("{%class%}", unit.Class.ToString());
                        playerReportBuilder.Replace("{%classname%}", ClassManager.Singleton.GetClassName(unit.Class));
                        playerReportBuilder.Replace("{%name%}", HttpUtility.HtmlEncode(unit.Name));

                        StringBuilder eventsBuilder = new StringBuilder();

                        int damageInPoint = 0;
                        int damageOutPoint = 0;
                        int healInPoint = 0;
                        int healOutPoint = 0;
                        int actionInPoint = 0;
                        int actionOutPoint = 0;

                        bool trimBegin = true;

                        for (int i = 0; i <= (EndTime - StartTime).TotalSeconds + 1; ++i)
                        {
                            DateTime timePoint = StartTime.AddSeconds(i);

                            StringBuilder playerNameBuilder = new StringBuilder();
                            playerNameBuilder.Append(playerNameTemplate);
                            playerNameBuilder.Replace("{%guid%}", unit.GUID.ToString());
                            playerNameBuilder.Replace("{%class%}", unit.Class.ToString());
                            playerNameBuilder.Replace("{%name%}", HttpUtility.HtmlEncode(unit.Name));

                            StringBuilder eventBuilder = new StringBuilder();
                            eventBuilder.Append(eventTemplate);
                            eventBuilder.Replace("{%time%}", HttpUtility.HtmlEncode(timePoint.ToString("HH:mm:ss", CultureInfo.InvariantCulture)));
                            eventBuilder.Replace("{%playername%}", playerNameBuilder.ToString());

                            StringBuilder damageInBuilder = new StringBuilder();
                            StringBuilder damageOutBuilder = new StringBuilder();
                            StringBuilder healInBuilder = new StringBuilder();
                            StringBuilder healOutBuilder = new StringBuilder();
                            StringBuilder actionInBuilder = new StringBuilder();
                            StringBuilder actionOutBuilder = new StringBuilder();

                            while (damageInPoint < unit.DamageIn.Count && unit.DamageIn[damageInPoint].Time <= timePoint)
                            {
                                StringBuilder eventItemBuilder = new StringBuilder();
                                eventItemBuilder.Append(eventItemTemplate);

                                eventItemBuilder.Replace("{%class%}", unit.DamageIn[damageInPoint].GetClassForIn(this).ToString());
                                eventItemBuilder.Replace("{%type%}", unit.DamageIn[damageInPoint].GetItemType().ToString());
                                eventItemBuilder.Replace("{%width%}", unit.DamageIn[damageInPoint].GetWidthForEventItem().ToString());
                                eventItemBuilder.Replace("{%eventtext%}", unit.DamageIn[damageInPoint].GetShortDescriptionText());
                                eventItemBuilder.Replace("{%tooltip%}", HttpUtility.HtmlEncode(unit.DamageIn[damageInPoint].GetLongDescriptionText()));

                                damageInBuilder.Append(eventItemBuilder.ToString());

                                damageInPoint++;
                            }

                            while (damageOutPoint < unit.DamageOut.Count && unit.DamageOut[damageOutPoint].Time <= timePoint)
                            {
                                StringBuilder eventItemBuilder = new StringBuilder();
                                eventItemBuilder.Append(eventItemTemplate);

                                eventItemBuilder.Replace("{%class%}", unit.DamageOut[damageOutPoint].GetClassForOut(this).ToString());
                                eventItemBuilder.Replace("{%type%}", unit.DamageOut[damageOutPoint].GetItemType().ToString());
                                eventItemBuilder.Replace("{%width%}", unit.DamageOut[damageOutPoint].GetWidthForEventItem().ToString());
                                eventItemBuilder.Replace("{%eventtext%}", unit.DamageOut[damageOutPoint].GetShortDescriptionText());
                                eventItemBuilder.Replace("{%tooltip%}", HttpUtility.HtmlEncode(unit.DamageOut[damageOutPoint].GetLongDescriptionText()));

                                damageOutBuilder.Append(eventItemBuilder.ToString());

                                damageOutPoint++;
                            }

                            while (healInPoint < unit.HealIn.Count && unit.HealIn[healInPoint].Time <= timePoint)
                            {
                                StringBuilder eventItemBuilder = new StringBuilder();
                                eventItemBuilder.Append(eventItemTemplate);

                                eventItemBuilder.Replace("{%class%}", unit.HealIn[healInPoint].GetClassForIn(this).ToString());
                                eventItemBuilder.Replace("{%type%}", unit.HealIn[healInPoint].GetItemType().ToString());
                                eventItemBuilder.Replace("{%width%}", unit.HealIn[healInPoint].GetWidthForEventItem().ToString());
                                eventItemBuilder.Replace("{%eventtext%}", unit.HealIn[healInPoint].GetShortDescriptionText());
                                eventItemBuilder.Replace("{%tooltip%}", HttpUtility.HtmlEncode(unit.HealIn[healInPoint].GetLongDescriptionText()));

                                healInBuilder.Append(eventItemBuilder.ToString());

                                healInPoint++;
                            }

                            while (healOutPoint < unit.HealOut.Count && unit.HealOut[healOutPoint].Time <= timePoint)
                            {
                                StringBuilder eventItemBuilder = new StringBuilder();
                                eventItemBuilder.Append(eventItemTemplate);

                                eventItemBuilder.Replace("{%class%}", unit.HealOut[healOutPoint].GetClassForOut(this).ToString());
                                eventItemBuilder.Replace("{%type%}", unit.HealOut[healOutPoint].GetItemType().ToString());
                                eventItemBuilder.Replace("{%width%}", unit.HealOut[healOutPoint].GetWidthForEventItem().ToString());
                                eventItemBuilder.Replace("{%eventtext%}", unit.HealOut[healOutPoint].GetShortDescriptionText());
                                eventItemBuilder.Replace("{%tooltip%}", HttpUtility.HtmlEncode(unit.HealOut[healOutPoint].GetLongDescriptionText()));

                                healOutBuilder.Append(eventItemBuilder.ToString());

                                healOutPoint++;
                            }

                            while (actionInPoint < unit.ActionIn.Count && unit.ActionIn[actionInPoint].Time <= timePoint)
                            {
                                StringBuilder eventItemBuilder = new StringBuilder();
                                eventItemBuilder.Append(eventItemTemplate);

                                eventItemBuilder.Replace("{%class%}", unit.ActionIn[actionInPoint].GetClassForIn(this).ToString());
                                eventItemBuilder.Replace("{%type%}", unit.ActionIn[actionInPoint].GetItemType().ToString());
                                eventItemBuilder.Replace("{%width%}", unit.ActionIn[actionInPoint].GetWidthForEventItem().ToString());
                                eventItemBuilder.Replace("{%eventtext%}", unit.ActionIn[actionInPoint].GetShortDescriptionText());
                                eventItemBuilder.Replace("{%tooltip%}", HttpUtility.HtmlEncode(unit.ActionIn[actionInPoint].GetLongDescriptionText()));

                                actionInBuilder.Append(eventItemBuilder.ToString());

                                actionInPoint++;
                            }

                            while (actionOutPoint < unit.ActionOut.Count && unit.ActionOut[actionOutPoint].Time <= timePoint)
                            {
                                StringBuilder eventItemBuilder = new StringBuilder();
                                eventItemBuilder.Append(eventItemTemplate);

                                eventItemBuilder.Replace("{%class%}", unit.ActionOut[actionOutPoint].GetClassForOut(this).ToString());
                                eventItemBuilder.Replace("{%type%}", unit.ActionOut[actionOutPoint].GetItemType().ToString());
                                eventItemBuilder.Replace("{%width%}", unit.ActionOut[actionOutPoint].GetWidthForEventItem().ToString());
                                eventItemBuilder.Replace("{%eventtext%}", unit.ActionOut[actionOutPoint].GetShortDescriptionText());
                                eventItemBuilder.Replace("{%tooltip%}", HttpUtility.HtmlEncode(unit.ActionOut[actionOutPoint].GetLongDescriptionText()));

                                actionOutBuilder.Append(eventItemBuilder.ToString());

                                actionOutPoint++;
                            }

                            eventBuilder.Replace("{%damagein%}", damageInBuilder.ToString());
                            eventBuilder.Replace("{%healin%}", healInBuilder.ToString());
                            eventBuilder.Replace("{%damageout%}", damageOutBuilder.ToString());
                            eventBuilder.Replace("{%healout%}", healOutBuilder.ToString());
                            eventBuilder.Replace("{%actionin%}", actionInBuilder.ToString());
                            eventBuilder.Replace("{%actionout%}", actionOutBuilder.ToString());


                            if (trimBegin)
                            {
                                if (damageInBuilder.Length > 0 || damageOutBuilder.Length > 0 || healInBuilder.Length > 0 || healOutBuilder.Length > 0 || actionInBuilder.Length > 0 || actionOutBuilder.Length > 0)
                                {
                                    eventsBuilder.Append(eventBuilder.ToString());
                                    trimBegin = false;
                                }
                            }
                            else
                            {
                                eventsBuilder.Append(eventBuilder.ToString());
                            }
                        }
                        playerReportBuilder.Replace("{%events%}", eventsBuilder.ToString());

                        playerReportsBuilder.Append(playerReportBuilder.ToString());
                    }
                }

                reportBuilder.Replace("{%playerreports%}", playerReportsBuilder.ToString());

                result = reportBuilder.ToString();
            //}
            //catch (Exception ex)
            //{
            //    result = String.Format("生成报告时发生错误: {0}", ex.Message);
            //}

            return result;
        }
    }
}
